package ltd.ninefish.framework.mybatisplus.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.override.MybatisMapperProxy;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.AopUtils;
import ltd.ninefish.framework.mybatisplus.service.DtoBaseService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author zhaoxiubin
 * create-time 2023-12-17 13:43
 * description DTO服务实现基类（ 泛型：M 是 mapper 对象，T 是实体，D 是DTO对象）
 **/
@SuppressWarnings("unchecked")
public class DtoBaseServiceImpl<M extends BaseMapper<T>, T, D> extends ServiceImpl<M, T> implements DtoBaseService<T, D> {
    protected final Class<?>[] typeArguments = GenericTypeUtils.resolveTypeArguments(getClass(), DtoBaseServiceImpl.class);

    private volatile SqlSessionFactory sqlSessionFactory;

    protected SqlSessionFactory getSqlSessionFactory() {
        if (this.sqlSessionFactory == null) {
            synchronized (this) {
                if (this.sqlSessionFactory == null) {
                    Object target = this.baseMapper;
                    if (AopUtils.isLoadSpringAop() && org.springframework.aop.support.AopUtils.isAopProxy(this.baseMapper)) {
                        target = AopProxyUtils.getSingletonTarget(this.baseMapper);
                    }

                    if (target != null) {
                        MybatisMapperProxy mybatisMapperProxy = (MybatisMapperProxy) Proxy.getInvocationHandler(target);
                        SqlSessionTemplate sqlSessionTemplate = (SqlSessionTemplate) mybatisMapperProxy.getSqlSession();
                        this.sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
                    } else {
                        this.sqlSessionFactory = GlobalConfigUtils.currentSessionFactory(this.entityClass);
                    }
                }
            }
        }

        return this.sqlSessionFactory;
    }

    protected final Class<D> dtoClass = currentDtoClass();

    @Override
    public Class<D> getDtoClass() {
        return dtoClass;
    }

    /**
     * 根据params获取QueryWrapper
     *
     * @param params
     * @return
     */
    @Override
    public QueryWrapper<T> getWrapper(QueryWrapper<T> queryWrapper, Map<String, Map<String, Object>> params) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        Iterator<Map.Entry<String, Map<String, Object>>> iteratorSymbols = params.entrySet().iterator();
        while (iteratorSymbols.hasNext()) {
            Map.Entry<String, Map<String, Object>> entrySymbol = iteratorSymbols.next();
            Map<String, Object> entryFieldAndValue = entrySymbol.getValue();
            switch (entrySymbol.getKey()) {
                case "eq":
                    wrapper.eq(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "ne":
                    wrapper.ne(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "lt":
                    wrapper.lt(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "ltSql":
                    wrapper.ltSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "le":
                    wrapper.le(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "leSql":
                    wrapper.leSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "gt":
                    wrapper.gt(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "gtSql":
                    wrapper.gtSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "ge":
                    wrapper.ge(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "geSql":
                    wrapper.geSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "like":
                    wrapper.like(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "notLike":
                    wrapper.notLike(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "likeLeft":
                    wrapper.likeLeft(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "notLikeLeft":
                    wrapper.notLikeLeft(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "likeRight":
                    wrapper.likeRight(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "notLikeRight":
                    wrapper.notLikeRight(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "in":
                    wrapper.in(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "notIn":
                    wrapper.notIn(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "inSql":
                    wrapper.inSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "notInSql":
                    wrapper.notInSql(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), Convert.toStr(entryFieldAndValue.get("values")));
                    break;
                case "between":
                    wrapper.between(Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("value1"), entryFieldAndValue.get("value2"));
                    break;
                case "notBetween":
                    wrapper.between(Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("value1"), entryFieldAndValue.get("value2"));
                    break;
                case "isNull":
                    wrapper.isNull(Convert.toStr(entryFieldAndValue.get("field")));
                    break;
                case "isNotNull":
                    wrapper.isNotNull(Convert.toStr(entryFieldAndValue.get("field")));
                    break;
                case "exists":
                    wrapper.exists(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "notExists":
                    wrapper.notExists(ObjUtil.isNull(entryFieldAndValue.get("values")), Convert.toStr(entryFieldAndValue.get("field")), entryFieldAndValue.get("values"));
                    break;
                case "or":
                    wrapper.or();
                    break;
                case "and":
                    queryWrapper.and((Consumer<QueryWrapper<T>>) wrapper);
                    break;
            }
        }
        return queryWrapper;
    }

    protected Class<D> currentDtoClass() {
        return (Class<D>) this.typeArguments[2];
    }

    /**
     * 插入一条记录（选择字段，策略插入）
     *
     * @param dto DTO对象
     * @return
     */
    @Override
    public boolean create(D dto) {
        T entity = BeanUtil.copyProperties(dto, entityClass);
        boolean resutl = save(entity);
        BeanUtils.copyProperties(entity, dto);
        return resutl;
    }

    /**
     * 插入（批量）
     *
     * @param dtoList DTO对象对象集合
     * @return
     */
    @Override
    public boolean createBatch(Collection<D> dtoList) {
        return createBatch(dtoList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 插入（批量）
     *
     * @param dtoList   DTO对象对象集合
     * @param batchSize 插入批次数量
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean createBatch(Collection<D> dtoList, int batchSize) {
        Collection<T> entityList = BeanUtil.copyToList(dtoList, entityClass);
        boolean resutl = saveBatch(entityList, batchSize);
        dtoList = BeanUtil.copyToList(entityList, dtoClass);
        return resutl;
    }

    /**
     * TableId 注解存在更新记录，否插入一条记录
     *
     * @param dto DTO对象
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean createOrModify(D dto) {
        T entity = BeanUtil.copyProperties(dto, entityClass);
        boolean resutl = saveOrUpdate(entity);
        BeanUtils.copyProperties(entity, dto);
        return resutl;
    }

    /**
     * 批量修改插入
     *
     * @param dtoList DTO对象对象集合
     */
    @Override
    public boolean createOrModifyBatch(Collection<D> dtoList) {
        return createOrModifyBatch(dtoList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 批量修改插入
     *
     * @param dtoList   DTO对象对象集合
     * @param batchSize 每次的数量
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean createOrModifyBatch(Collection<D> dtoList, int batchSize) {
        Collection<T> entityList = BeanUtil.copyToList(dtoList, entityClass);
        boolean resutl = saveOrUpdateBatch(entityList, batchSize);
        dtoList = BeanUtil.copyToList(entityList, dtoClass);
        return resutl;
    }

    /**
     * 根据实体(ID)删除
     *
     * @param dto DTO对象
     * @return 删除结果
     */
    @Override
    public boolean deleteById(D dto) {
        T entity = BeanUtil.copyProperties(dto, entityClass);
        boolean resutl = removeById(entity);
        BeanUtils.copyProperties(entity, dto);
        return resutl;
    }

    /**
     * 根据 ID 选择修改
     *
     * @param dto DTO对象
     */
    @Override
    public boolean modifyById(D dto) {
        T entity = BeanUtil.copyProperties(dto, entityClass);
        boolean resutl = updateById(entity);
        BeanUtils.copyProperties(entity, dto);
        return resutl;
    }

    /**
     * 根据ID 批量更新
     *
     * @param dtoList DTO对象集合
     */
    @Override
    public boolean modifyBatchById(Collection<D> dtoList) {
        return modifyBatchById(dtoList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 根据ID 批量更新
     *
     * @param dtoList   DTO对象集合
     * @param batchSize 更新批次数量
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean modifyBatchById(Collection<D> dtoList, int batchSize) {
        Collection<T> entityList = BeanUtil.copyToList(dtoList, entityClass);
        boolean resutl = updateBatchById(entityList, batchSize);
        dtoList = BeanUtil.copyToList(entityList, dtoClass);
        return resutl;
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    @Override
    public D queryById(Serializable id) {
        T entity = getById(id);
        return BeanUtil.copyProperties(entity, dtoClass);
    }

    /**
     * 根据 ID 查询，返回一个Option对象
     *
     * @param id 主键ID
     * @return {@link Optional}
     */
    @Override
    public Optional<D> queryOptById(Serializable id) {
        return Optional.ofNullable(queryById(id));
    }

    /**
     * 查询（根据ID 批量查询）
     *
     * @param idList 主键ID列表
     */
    @Override
    public List<D> queryByIds(Collection<? extends Serializable> idList) {
        List<T> entityList = listByIds(idList);
        return BeanUtil.copyToList(entityList, dtoClass);
    }

    /**
     * 查询（根据 columnMap 条件）
     *
     * @param columnMap 表字段 map 对象
     */
    @Override
    public List<D> queryByMap(Map<String, Object> columnMap) {
        List<T> entityList = listByMap(columnMap);
        return BeanUtil.copyToList(entityList, dtoClass);
    }

    /**
     * 根据 Wrapper，查询一条记录 <br/>
     * <p>结果集，如果是多个会抛出异常，随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    @Override
    public D queryOne(Wrapper<T> queryWrapper) {
        return queryOne(queryWrapper, true);
    }

    /**
     * 根据 Wrapper，查询一条记录 <br/>
     * <p>结果集，如果是多个会抛出异常，随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     * @return {@link Optional} 返回一个Optional对象
     */
    @Override
    public Optional<D> queryOneOpt(Wrapper<T> queryWrapper) {
        return queryOneOpt(queryWrapper, true);
    }

    /**
     * 根据 Wrapper，查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     * @param throwEx      有多个 result 是否抛出异常
     */
    @Override
    public D queryOne(Wrapper<T> queryWrapper, boolean throwEx) {
        T entity = getOne(queryWrapper, throwEx);
        return BeanUtil.copyProperties(entity, dtoClass);
    }

    /**
     * 根据 Wrapper，查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     * @param throwEx      有多个 result 是否抛出异常
     * @return {@link Optional} 返回一个Optional对象
     */
    @Override
    public Optional<D> queryOneOpt(Wrapper<T> queryWrapper, boolean throwEx) {
        T entity = getOne(queryWrapper, throwEx);
        return Optional.ofNullable(BeanUtil.copyProperties(entity, dtoClass));
    }

    /**
     * 查询所有
     *
     * @see Wrappers#emptyWrapper()
     */
    @Override
    public List<D> getList() {
        return getList(Wrappers.emptyWrapper());
    }

    /**
     * 查询列表
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    @Override
    public List<D> getList(Wrapper<T> queryWrapper) {
        List<T> entityList = list(queryWrapper);
        return BeanUtil.copyToList(entityList, dtoClass);
    }
}
